// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/base/order/risk_pending_order.h"

#include <fmt/format.h>
#include <glog/logging.h>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin2/exchange/base/order/order_context_manager.h"

constexpr auto kPendingOrderWaitTime = 30'000'000'000LL;

namespace coin2::exchange::base::order {

RiskPendingOrder::RiskPendingOrder(int threshold, int64_t window_size)
    : risk_pending_order_threshold_(threshold), pending_order_window_(this) {
  CHECK(risk_pending_order_threshold_ > 0);
  CHECK(window_size > 0);
  pending_order_window_.ResizeWindow(window_size);
}

void RiskPendingOrder::HandleOrderTimedout(int64_t ts, const OrderContext& oc) {
  pending_order_window_.Push(ts, oc.proc_order_id());
}

void RiskPendingOrder::HandleOrderVerified(int64_t ts, const OrderContext& oc) {
  DelOrder(ts, oc.proc_order_id());
}

void RiskPendingOrder::ManageRisk(int64_t ts) {
  // check every 5s
  if (last_update_timestamp_ + 5'000'000'000LL > ts) {
    return;
  }
  last_update_timestamp_ = ts;

  // first check
  Update(ts);
  const auto pending_order_cnt = pending_orders_.size();
  if (pending_order_cnt < risk_pending_order_threshold_) {
    return;
  }

  // second check
  std::string debug_str;
  int risk_order_cnt = 0;
  std::unordered_map<int64_t, int64_t> pending_orders_tmp;
  for (const auto& item : pending_orders_) {
    if (item.second + kPendingOrderWaitTime > ts) {
      pending_orders_tmp.emplace(item);
      continue;
    }
    ++risk_order_cnt;
    debug_str.append(fmt::format("{},", item.first));
  }
  if (risk_order_cnt < risk_pending_order_threshold_) {
    return;
  }
  pending_orders_.swap(pending_orders_tmp);

  LOG(WARNING) << fmt::format("[RiskPendingOrder] total: {} <{}>", risk_order_cnt, debug_str);
  HandleRiskEvent(risk_order_cnt);
}

void RiskPendingOrder::AddOrder(int64_t ts, int64_t proc_order_id) {
  if (pending_orders_.count(proc_order_id) != 0) {
    return;
  }
  pending_orders_.emplace(proc_order_id, ts);
}

void RiskPendingOrder::DelOrder(int64_t ts, int64_t proc_order_id) {
  if (pending_orders_.count(proc_order_id) == 0) {
    return;
  }
  pending_orders_.erase(proc_order_id);
}

void RiskPendingOrder::Update(int64_t ts) { pending_order_window_.PushTime(ts); }

void RiskPendingOrder::HandleRiskEvent(int risk_pending_order_cnt) {
  coin::proto::RiskEvent risk_event;
  risk_event.set_risk_event_type(coin::proto::RiskEvent::RISK_PENDING_ORDER);
  risk_event.set_risk_pending_order_window_size(pending_order_window_.window_size());
  risk_event.set_risk_pending_order_count(risk_pending_order_cnt);
  risk_event_handler_(risk_event);
}

}  // namespace coin2::exchange::base::order
